// コンソールドライバ
// 送信は優先度が下の任意のスレッドから可能。優先度により初期化が保証される。
// 受信は登録したメッセージボックスからのみできる(USEコマンド)。

#include "defines.h"
#include "kozos.h"
#include "intr.h"
#include "interrupt.h"
#include "serial.h"
#include "lib.h"
#include "consdrv.h"

#define SEND_BUFFER_SIZE 256

static int cons_used;

static unsigned char *send_buf; // リングバッファ
static int send_head;
static int send_len;

static kz_msgbox_id_t msgbox_in;
static kz_msgbox_id_t msgbox_out;

int send_to_cons(int size, char *command)
{
    if (msgbox_out != MSGBOX_NULL) {
        return kz_send(msgbox_out, size, command);
    } else {
        return -1;
    }
}

static void send_char()
{
    serial_send_byte(SERIAL_DEFAULT_DEVICE, send_buf[send_head]);

    if (++send_head == SEND_BUFFER_SIZE)
        send_head = 0;
    send_len--;
}

// バッファを変更するので割込禁止で呼ぶこと
static void buffer_char(unsigned char c)
{
    if (c == '\n') {
        if (send_len >= SEND_BUFFER_SIZE - 1)
            kz_sysdown("buffer_char: buffer overflow");
        send_buf[(send_head + send_len++) % SEND_BUFFER_SIZE] = '\r';
    } else {
        if (send_len >= SEND_BUFFER_SIZE)
            kz_sysdown("buffer_char: buffer overflow");
    }
    send_buf[(send_head + send_len++) % SEND_BUFFER_SIZE] = c;

    if (!serial_intr_is_send_enable(SERIAL_DEFAULT_DEVICE)) {
        serial_intr_send_enable(SERIAL_DEFAULT_DEVICE);
        send_char();
    }
}

static void consdrv_intr()
{
    if (serial_is_error(SERIAL_DEFAULT_DEVICE)) {
        puts("\nserial error !!\n");
        serial_error_clear(SERIAL_DEFAULT_DEVICE); /* とりあえずクリアして回復を願う */
    }

    if (serial_is_recv_enable(SERIAL_DEFAULT_DEVICE)) {
        int c = serial_recv_byte(SERIAL_DEFAULT_DEVICE);

        if (cons_used) {

            kx_send(msgbox_in, c, "w");

        } else {
            // 単にエコーバック
            if (c == '\r') c = '\n';
            buffer_char((unsigned char)c);
        }
    }

    if (serial_is_send_enable(SERIAL_DEFAULT_DEVICE)) {
        if (send_len) {
            send_char();
        } else {
            serial_intr_send_disable(SERIAL_DEFAULT_DEVICE);
        }
    }
}

static int consdrv_command(int size, char *command)
{
    consdrv_cmd_header *hdr = (consdrv_cmd_header *)command;

    switch (hdr->cmd) {
        case CONSDRV_CMD_USE:
            if (cons_used) {
                kz_send(hdr->mb, 0, "u");

            } else {
                kz_send(hdr->mb, 1, "u");
                msgbox_in = hdr->mb;
                cons_used = 1;
            }
            break;

        case CONSDRV_CMD_WRITE:
            INTR_DISABLE;
            buffer_char((unsigned char)hdr->car);
            INTR_ENABLE;
            break;

        case CONSDRV_CMD_CLOSE:
            if (hdr->mb == msgbox_in) {
                cons_used = 0;
                kz_send(hdr->mb, 1, "c");
            } else {
                kz_send(hdr->mb, 0, "c");
            }
            break;

        default:
            break;
    }

    return 0;
}

int consdrv_main(int argc, char *argv[])
{
    int size;
    char *p;

    cons_used = 0;

    send_buf = kz_kmalloc(SEND_BUFFER_SIZE);
    send_len = 0;
    send_head = 0;
    msgbox_out = kz_getmsgbox();

    kz_setintr(SOFTVEC_TYPE_SERINTR, consdrv_intr);
    serial_intr_recv_enable(SERIAL_DEFAULT_DEVICE); /* 受信スレッドがなくてもエコーバックはする */

    while (1) {
        kz_recv(msgbox_out, &size, &p, 0);
        consdrv_command(size, p);
        kz_kmfree(p);
    }

    /* ここには来ない(落ちることは考えてない)。 */

    return 0;
}
